import asyncio
from functools import wraps
from typing import Any, Dict

from redis.exceptions import RedisError
from tortoise.transactions import in_transaction

from app.core.config import settings
from app.core.constants import ChangeType, TaskStatus
from app.core.task.scheduler import Scheduler
from app.core.task.store.redis import RedisStore
from app.core.task.task_log import scheduler_logger
from app.models.job_model import JobChangeLogModel, JobModel, JobExecutionLogModel
from app.models.user_model import UserModel
from app.schemas.job import SchedulerJob, JobCreate, JobUpdate
from app.core.task.time_wheel import RedisTimeWheel
from app.utils.timezone import now

from app.core.redis_conn import redis_helper


# 自定义异常类
class TaskManagerException(Exception):
    """任务管理器基础异常类"""

    pass


class TaskNotFoundException(TaskManagerException):
    """任务未找到异常"""

    pass


class TaskValidationError(TaskManagerException):
    """任务验证错误"""

    pass


class TaskSchedulingError(TaskManagerException):
    """任务调度错误"""

    pass


# 重试装饰器
def retry_on_redis_error(max_retries: int = 3, delay: float = 0.1):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            last_error = None
            for attempt in range(max_retries):
                try:
                    return await func(*args, **kwargs)
                except RedisError as e:
                    last_error = e
                    if attempt < max_retries - 1:
                        await asyncio.sleep(delay * (2**attempt))
            raise last_error

        return wrapper

    return decorator


class TaskManager:
    """
    TaskManager的主要功能

    1. 同步数据库内的任务到Redis中,且保证其能正常的在Scheduler中运行
    2. 对Redis中的任务进行增删改查
    3. 对任务进行暂停、恢复、终止等操作
    4. 对任务运行记录进行同步

    # 涉及接口
    - 任务的增删改查
        - create_job
            - 创建任务, 接受一个JobCreate对象
        - update_job
            - 更新任务, 接受一个JobUpdate对象
        - delete_job
            - 删除任务, 接受一个job_id
        - get_job
            - 获取任务详情, 接受一个job_id
        - list_jobs
            - 获取任务列表, 接受一个filters字���, page, page_size
    - 任务同步
        - sync_db_with_redis
            - 同步数据库中的任务数据到Redis中
        - sync_execution_log_with_db
            - 同步任务执行记录到数据库中

        - sync_specified_job
            - 同步指定任务的数据到Redis中, 具体实现应该与 update_job 一致

    - 任务的暂停、恢复、终止等操作
        - pause_job
            - 暂停任务, 接受一个 job_id 和 操作者
        - resume_job
            - 恢复任务, 接受一个 job_id 和 操作者
        - terminate_job
            - 终止任务, 接受一个 job_id 和 操作者
    """

    def __init__(self):
        """初始化任务管理器"""
        try:
            self.store = RedisStore()
            self.time_wheel = RedisTimeWheel()
            self.scheduler = Scheduler().get_instance()
            self.redis_client = redis_helper.get_connection(settings.TASK_REDIS_DB)
            self._task_cache = {}
            self._cache_ttl = 300
        except Exception as e:
            scheduler_logger.error(f"TaskManager初始化失败: {str(e)}", exc_info=True)
            raise TaskManagerException("TaskManager初始化失败") from e

    # 任务的增删改查
    @retry_on_redis_error()
    async def create_job(self, job_create: JobCreate, creator: UserModel) -> JobModel:
        """创建新任务"""
        try:
            async with in_transaction():
                # 创建任务记录
                job = await JobModel.create(**job_create.model_dump(), creator=creator)

                # 如果是活动状态，同步到Redis
                if job.status == TaskStatus.ACTIVE:
                    job_schema = SchedulerJob.model_validate(job)
                    await self.store.add_job(job_schema)
                    next_run_time = await job.trigger.get_next_fire_time(now())
                    if next_run_time:
                        await self.time_wheel.add_task(job.id, next_run_time)

                # 记录创建操作
                await self._record_action(
                    job=job, change_type=ChangeType.CREATE, changed_fields=job_create.model_dump(), user=creator
                )

                return job

        except Exception as e:
            scheduler_logger.error(f"创建任务失败: {str(e)}", exc_info=True)
            raise TaskManagerException("创建任务失败") from e

    @retry_on_redis_error()
    async def update_job(self, job_id: str, job_update: JobUpdate, operator: UserModel) -> JobModel:
        """更新任务"""
        try:
            job = await self.get_task(job_id)

            async with in_transaction():
                # 更新数据库
                update_data = job_update.model_dump(exclude_unset=True)
                await JobModel.filter(id=job_id).update(**update_data)

                # 重新获取更新后的任务
                updated_job = await self.get_task(job_id)

                # 同步到Redis
                if updated_job.status == TaskStatus.ACTIVE:
                    job_schema = SchedulerJob.model_validate(updated_job)
                    await self.store.update_job(job_id, job_schema)
                    next_run_time = await updated_job.trigger.get_next_fire_time(now())
                    if next_run_time:
                        await self.time_wheel.add_task(job_id, next_run_time)
                else:
                    # 非活动状态从Redis移除
                    await self.store.remove_job(job_id)
                    await self.time_wheel.remove_task(job_id)

                # 记录更新操作
                await self._record_action(
                    job=updated_job, change_type=ChangeType.UPDATE, changed_fields=update_data, user=operator
                )

                return updated_job

        except TaskNotFoundException:
            raise
        except Exception as e:
            scheduler_logger.error(f"更新任务 {job_id} 失败: {str(e)}", exc_info=True)
            raise TaskManagerException(f"更新任务失败") from e

    @retry_on_redis_error()
    async def delete_job(self, job_id: str, operator: UserModel) -> bool:
        """删除任务"""
        try:
            job = await self.get_task(job_id)

            async with in_transaction():
                # 从Redis移除
                await self.store.remove_job(job_id)
                await self.time_wheel.remove_task(job_id)

                # 取消正在运行的任务
                await self.scheduler.cancel_job(job_id)

                # 记录删除操作
                await self._record_action(job=job, change_type=ChangeType.DELETE, changed_fields={}, user=operator)

                # 从数据库删除
                await job.delete()

                # 清除缓存
                if job_id in self._task_cache:
                    del self._task_cache[job_id]

                return True

        except TaskNotFoundException:
            raise
        except Exception as e:
            scheduler_logger.error(f"删除任务 {job_id} 失败: {str(e)}", exc_info=True)
            return False

    async def get_task(self, job_id: str) -> JobModel:
        """从数据库获取任务，优先使用缓存"""
        # 检查缓存
        if job_id in self._task_cache:
            cached_job, cache_time = self._task_cache[job_id]
            if (now() - cache_time).total_seconds() < self._cache_ttl:
                return cached_job

        # 从数据库获取
        job = await JobModel.get_or_none(id=job_id)
        if not job:
            raise TaskNotFoundException(f"任务 {job_id} 不存在")

        # 更新缓存
        self._task_cache[job_id] = (job, now())
        return job

    @retry_on_redis_error()
    async def get_job(self, job_id: str) -> Dict[str, Any]:
        """获取任务详情"""
        try:
            # 获取基础任务信息
            job = await self.get_task(job_id)
            job_data = SchedulerJob.model_validate(job).model_dump()
            # 获取最近的操作记录
            recent_logs = await JobExecutionLogModel.filter(job_id=job_id).order_by("-execution_time").limit(5).all()
            job_data["recent_logs"] = [
                {"execution_time": log.execution_time, "status": log.status, "duration": log.duration}
                for log in recent_logs
            ]

            return job_data

        except TaskNotFoundException:
            raise
        except Exception as e:
            scheduler_logger.error(f"获取任务 {job_id} 详情失败: {str(e)}", exc_info=True)
            raise TaskManagerException("获取任务详情失败") from e

    async def list_jobs(self, filters: Dict[str, Any] = None, page: int = 1, page_size: int = 20) -> Dict[str, Any]:
        """获取任务列表"""
        try:
            # 构建查询条件
            query = JobModel.all()
            if filters:
                if "status" in filters:
                    query = query.filter(status=filters["status"])
                if "creator_id" in filters:
                    query = query.filter(creator_id=filters["creator_id"])
                if "name" in filters:
                    query = query.filter(name__icontains=filters["name"])

            # 计算总数
            total = await query.count()

            # 分页查询
            jobs = await query.offset((page - 1) * page_size).limit(page_size).all()

            # 转换为响应格式
            job_list = []
            for job in jobs:
                job_data = SchedulerJob.model_validate(job).model_dump()
                if job.status == TaskStatus.ACTIVE:
                    # 获取下次运行时间
                    job_data["next_run_time"] = await self.time_wheel.get_next_run_time(job.id)
                job_list.append(job_data)

            return {"total": total, "items": job_list, "page": page, "page_size": page_size}

        except Exception as e:
            scheduler_logger.error(f"获取任务列表失败: {str(e)}", exc_info=True)
            raise TaskManagerException("获取任务列表失败") from e

    # 任务同步相关方法
    @retry_on_redis_error()
    async def sync_db_with_redis(self) -> None:
        """同步数据库任务到Redis"""
        # 清空Redis中的任务
        await self.store.clear_jobs()
        await self.time_wheel.clear_tasks()
        await self.store.clear_job_execution_logs()
        try:
            # 获取所有活动任务
            active_tasks = await JobModel.filter(status=TaskStatus.ACTIVE).all()
            jobs = [SchedulerJob.model_validate(task) for task in active_tasks]

            # 获取Redis中的任务ID
            redis_task_ids = await self.store.get_all_job_ids()

            # 同步数据库到Redis
            for task in jobs:
                if task.id not in redis_task_ids:
                    await self.store.add_job(task)
                    next_run_time = await task.trigger.get_next_fire_time(now())
                    if next_run_time:
                        await self.time_wheel.add_task(task.id, next_run_time)

            # 清理Redis中多余的任务
            for task_id in redis_task_ids:
                if task_id not in [t.id for t in jobs]:
                    await self.store.remove_job(task_id)
                    await self.time_wheel.remove_task(task_id)
                    await self.scheduler.cancel_job(task_id)

            scheduler_logger.info("数据同步完成")

        except Exception as e:
            scheduler_logger.error(f"数据同步失败: {str(e)}", exc_info=True)
            raise TaskManagerException("数据同步失败") from e

    @retry_on_redis_error()
    async def sync_specified_job(self, job_id: str) -> bool:
        """同步指定任务到Redis"""
        try:
            job = await self.get_task(job_id)
            job_schema = SchedulerJob.model_validate(job)

            # 更新Redis存储
            if job.status == TaskStatus.ACTIVE:
                await self.store.update_job(job_id, job_schema)
                next_run_time = await job.trigger.get_next_fire_time(now())
                if next_run_time:
                    await self.time_wheel.add_task(job_id, next_run_time)
            else:
                await self.store.remove_job(job_id)
                await self.time_wheel.remove_task(job_id)

            scheduler_logger.info(f"任务 {job_id} 同步完成")
            return True

        except Exception as e:
            scheduler_logger.error(f"同步任务 {job_id} 失败: {str(e)}", exc_info=True)
            return False

    async def _record_action(self, job: JobModel, change_type: str, changed_fields: dict, user: UserModel) -> None:
        """记录任务操作"""
        try:
            await JobChangeLogModel.create(
                job=job, operator=user, change_type=change_type, changed_fields=changed_fields
            )
        except Exception as e:
            scheduler_logger.error(f"记录任务操作失败: {str(e)}", exc_info=True)
